﻿using Core;
using Core.Authorization;
using Core.Helpers;
using Core.Migrations;
using Core.Persistence;
using Core.Tenant.Abstractions;
using Finbuckle.MultiTenant;
using Finbuckle.MultiTenant.Abstractions;
using Finbuckle.MultiTenant.Stores.DistributedCacheStore;
using FrameworkCore.Migrations;
using Infrastructure.Persistence;
using Infrastructure.Persistence.Services;
using Infrastructure.Tenant.Persistence;
using Infrastructure.Tenant.Services;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Serilog;

namespace Infrastructure.Tenant;

#nullable disable

internal static class Extensions
{
    public static IServiceCollection ConfigureMultitenancy(this IServiceCollection services)
    {
        ArgumentNullException.ThrowIfNull(services);
        services.AddTransient<IConnectionStringValidator, ConnectionStringValidator>();
        services.BindDbContext<TenantDbContext>();
        services
            .AddMultiTenant<BoilerplateTenantInfo>(config =>
            {
                // to save database calls to resolve tenant
                // this was happening for every request earlier, leading to ineffeciency
                config.Events.OnTenantResolveCompleted = async (context) =>
                {
                    if (context.MultiTenantContext.StoreInfo is null) return;
                    if (context.MultiTenantContext.StoreInfo.StoreType != typeof(DistributedCacheStore<BoilerplateTenantInfo>))
                    {
                        var sp = ((HttpContext)context.Context!).RequestServices;
                        var distributedCacheStore = sp
                            .GetService<IEnumerable<IMultiTenantStore<BoilerplateTenantInfo>>>()!
                            .FirstOrDefault(s => s.GetType() == typeof(DistributedCacheStore<BoilerplateTenantInfo>));

                        await distributedCacheStore!.TryAddAsync(context.MultiTenantContext.TenantInfo!);
                    }
                    await Task.FromResult(0);
                };
            })
            .WithClaimStrategy(BoilerplateClaims.Tenant)
            .WithHeaderStrategy(TenantConstants.Identifier)
            .WithDelegateStrategy(async context =>
            {
                if (context is not HttpContext httpContext)
                    return null;
                if (!httpContext.Request.Query.TryGetValue("tenant", out var tenantIdentifier) || string.IsNullOrEmpty(tenantIdentifier))
                    return null;
                return await Task.FromResult(tenantIdentifier.ToString());
            })
            .WithDistributedCacheStore(TimeSpan.FromMinutes(60))
            .WithEFCoreStore<TenantDbContext, BoilerplateTenantInfo>();
        services.AddScoped<ITenantService, TenantService>();
        return services;
    }
    public static IApplicationBuilder UseMultitenancy(this IApplicationBuilder app)
    {
        ArgumentNullException.ThrowIfNull(app);
        app.UseMultiTenant();

        //// set up tenant store
        var tenants = TenantStoreSetup(app);

        //// set up tenant databases
        app.SetupTenantDatabases(tenants);

        return app;
    }

    private static IApplicationBuilder SetupTenantDatabases(this IApplicationBuilder app, IEnumerable<BoilerplateTenantInfo> tenants)
    {
        //获取迁移
        var assemblies = AssemblyDiscoveryHelper.GetAllAssemblies(CommonHelper.DefaultFileProvider);


        foreach (var tenant in tenants)
        {
            // create a scope for tenant
            using var tenantScope = app.ApplicationServices.CreateScope();

            //set current tenant so that the right connection string is used
            tenantScope.ServiceProvider.GetRequiredService<IMultiTenantContextSetter>()
                .MultiTenantContext = new MultiTenantContext<BoilerplateTenantInfo>()
                {
                    TenantInfo = tenant
                };

            if (!string.IsNullOrWhiteSpace(tenant.ConnectionString))//创建数据库
            {
                var dbConfig = tenantScope.ServiceProvider.GetRequiredService<IOptions<DatabaseOptions>>().Value;
                var dataProvider = tenantScope.ServiceProvider.GetRequiredService<IBoilerplateDataProvider>();
                MigrationExecutor.ExecuteMigrationsFromDlls(assemblies.Select(s => s.Key), tenant.ConnectionString, dbConfig, dataProvider);
            }

            // using the scope, perform migrations / seeding
            var initializers = tenantScope.ServiceProvider.GetServices<IDbInitializer>();
            foreach (var initializer in initializers)
            {
                //initializer.MigrateAsync(CancellationToken.None).Wait();
                initializer.SeedAsync(CancellationToken.None).Wait();
            }
        }
        return app;
    }

    private static IEnumerable<BoilerplateTenantInfo> TenantStoreSetup(IApplicationBuilder app)
    {
        var scope = app.ApplicationServices.CreateScope();

        // tenant master schema migration
        var tenantDbContext = scope.ServiceProvider.GetRequiredService<TenantDbContext>();
        #region 数据库不用EF Core创建，故注释这里；以后可以迁移来这里创建数据库
        //if (tenantDbContext.Database.GetPendingMigrations().Any())
        //{
        //    //tenantDbContext.Database.Migrate();
        //    //Log.Information("applied database migrations for tenant module");
        //}
        #endregion

        #region 初始化宿主租户租户数据
        // default tenant seeding
        if (tenantDbContext.TenantInfo.Find(TenantConstants.Root.Id) is null)
        {
            var rootTenant = new BoilerplateTenantInfo(
                TenantConstants.Root.Id,
                TenantConstants.Root.Name,
                string.Empty,
                TenantConstants.Root.EmailAddress);

            rootTenant.SetValidity(DateTime.UtcNow.AddYears(1));
            tenantDbContext.TenantInfo.Add(rootTenant);
            tenantDbContext.SaveChanges();
            Log.Information("配置的默认租户数据");
        }
        #endregion

        // get all tenants from store
        var tenantStore = scope.ServiceProvider.GetRequiredService<IMultiTenantStore<BoilerplateTenantInfo>>();
        var tenants = tenantStore.GetAllAsync().Result;

        //dispose scope
        scope.Dispose();

        return tenants;
    }
}
